// Copyright (c) 2016-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
//    conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
//    of conditions and the following disclaimer in the documentation and/or other
//    materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
//    used to endorse or promote products derived from this software without specific
//    prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#pragma once

#include "string_tools.h"
#include "rapidjson/document.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "rpc/message_data_structs.h"
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
#include "common/sfinae_helpers.h"

#define OBJECT_HAS_MEMBER_OR_THROW(val, key)    \
  do                                            \
  {                                             \
    if (!val.HasMember(key))                    \
    {                                           \
      throw cryptonote::json::MISSING_KEY(key); \
    }                                           \
  } while (0);

#define INSERT_INTO_JSON_OBJECT(jsonVal, doc, key, source) \
  rapidjson::Value key##Val;                               \
  cryptonote::json::toJsonValue(doc, source, key##Val);    \
  jsonVal.AddMember(#key, key##Val, doc.GetAllocator());

#define GET_FROM_JSON_OBJECT(source, dst, key)                \
  OBJECT_HAS_MEMBER_OR_THROW(source, #key)                    \
  decltype(dst) dstVal##key;                                  \
  cryptonote::json::fromJsonValue(source[#key], dstVal##key); \
  dst = dstVal##key;

namespace cryptonote
{

  namespace json
  {

    struct JSON_ERROR : public std::exception
    {
    protected:
      JSON_ERROR() {}
      std::string m;

    public:
      virtual ~JSON_ERROR() {}

      const char *what() const throw()
      {
        return m.c_str();
      }
    };

    struct MISSING_KEY : public JSON_ERROR
    {
      MISSING_KEY(const char *key)
      {
        m = std::string("Key \"") + key + "\" missing from object.";
      }
    };

    struct WRONG_TYPE : public JSON_ERROR
    {
      WRONG_TYPE(const char *type)
      {
        m = std::string("Json value has incorrect type, expected: ") + type;
      }
    };

    struct BAD_INPUT : public JSON_ERROR
    {
      BAD_INPUT()
      {
        m = "An item failed to convert from json object to native object";
      }
    };

    struct PARSE_FAIL : public JSON_ERROR
    {
      PARSE_FAIL()
      {
        m = "Failed to parse the json request";
      }
    };

    template <typename Type>
    inline constexpr bool is_to_hex()
    {
      return std::is_pod<Type>() && !std::is_integral<Type>();
    }

    // POD to json value
    template <class Type>
    typename std::enable_if<is_to_hex<Type>()>::type toJsonValue(rapidjson::Document &doc, const Type &pod, rapidjson::Value &value)
    {
      value = rapidjson::Value(epee::string_tools::pod_to_hex(pod).c_str(), doc.GetAllocator());
    }

    template <class Type>
    typename std::enable_if<is_to_hex<Type>()>::type fromJsonValue(const rapidjson::Value &val, Type &t)
    {
      if (!val.IsString())
      {
        throw WRONG_TYPE("string");
      }

      // TODO: handle failure to convert hex string to POD type
      bool success = epee::string_tools::hex_to_pod(val.GetString(), t);

      if (!success)
      {
        throw BAD_INPUT();
      }
    }

    void toJsonValue(rapidjson::Document &doc, const std::string &i, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, std::string &str);

    void toJsonValue(rapidjson::Document &doc, bool i, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, bool &b);

    // integers overloads for toJsonValue are not needed for standard promotions

    void fromJsonValue(const rapidjson::Value &val, unsigned char &i);

    void fromJsonValue(const rapidjson::Value &val, signed char &i);

    void fromJsonValue(const rapidjson::Value &val, char &i);

    void fromJsonValue(const rapidjson::Value &val, unsigned short &i);

    void fromJsonValue(const rapidjson::Value &val, short &i);

    void toJsonValue(rapidjson::Document &doc, const unsigned i, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, unsigned &i);

    void toJsonValue(rapidjson::Document &doc, const int, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, int &i);

    void toJsonValue(rapidjson::Document &doc, const unsigned long long i, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, unsigned long long &i);

    void toJsonValue(rapidjson::Document &doc, const long long i, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, long long &i);

    inline void toJsonValue(rapidjson::Document &doc, const unsigned long i, rapidjson::Value &val)
    {
      toJsonValue(doc, static_cast<unsigned long long>(i), val);
    }
    void fromJsonValue(const rapidjson::Value &val, unsigned long &i);

    inline void toJsonValue(rapidjson::Document &doc, const long i, rapidjson::Value &val)
    {
      toJsonValue(doc, static_cast<long long>(i), val);
    }
    void fromJsonValue(const rapidjson::Value &val, long &i);

    // end integers

    void toJsonValue(rapidjson::Document &doc, const cryptonote::transaction &tx, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::transaction &tx);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::block &b, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::block &b);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::txin_v &txin, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::txin_v &txin);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::txin_gen &txin, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::txin_gen &txin);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::txin_to_script &txin, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::txin_to_script &txin);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::txin_to_scripthash &txin, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::txin_to_scripthash &txin);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::txin_sispop_key &txin, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::txin_sispop_key &txin);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::txout_target_v &txout, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::txout_target_v &txout);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::txout_to_script &txout, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::txout_to_script &txout);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::txout_to_scripthash &txout, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::txout_to_scripthash &txout);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::txout_sispop_tagged_key &txout, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::txout_sispop_tagged_key &txout);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::tx_out &txout, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::tx_out &txout);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::connection_info &info, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::connection_info &info);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::block_complete_entry &blk, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::block_complete_entry &blk);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::block_with_transactions &blk, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::block_with_transactions &blk);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::transaction_info &tx_info, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::transaction_info &tx_info);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::output_key_and_amount_index &out, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::output_key_and_amount_index &out);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::amount_with_random_outputs &out, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::amount_with_random_outputs &out);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::peer &peer, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::peer &peer);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::tx_in_pool &tx, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::tx_in_pool &tx);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::hard_fork_info &info, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::hard_fork_info &info);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::output_amount_count &out, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::output_amount_count &out);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::output_amount_and_index &out, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::output_amount_and_index &out);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::output_key_mask_unlocked &out, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::output_key_mask_unlocked &out);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::error &err, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::error &error);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::BlockHeaderResponse &response, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::BlockHeaderResponse &response);

    void toJsonValue(rapidjson::Document &doc, const rct::rctSig &i, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &i, rct::rctSig &sig);

    void toJsonValue(rapidjson::Document &doc, const rct::ecdhTuple &tuple, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, rct::ecdhTuple &tuple);

    void toJsonValue(rapidjson::Document &doc, const rct::rangeSig &sig, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, rct::rangeSig &sig);

    void toJsonValue(rapidjson::Document &doc, const rct::Bulletproof &p, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, rct::Bulletproof &p);

    void toJsonValue(rapidjson::Document &doc, const rct::boroSig &sig, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, rct::boroSig &sig);

    void toJsonValue(rapidjson::Document &doc, const rct::mgSig &sig, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, rct::mgSig &sig);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::DaemonInfo &info, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::DaemonInfo &info);

    void toJsonValue(rapidjson::Document &doc, const cryptonote::rpc::output_distribution &dist, rapidjson::Value &val);
    void fromJsonValue(const rapidjson::Value &val, cryptonote::rpc::output_distribution &dist);

    template <typename Map>
    typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Document &doc, const Map &map, rapidjson::Value &val);

    template <typename Map>
    typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type fromJsonValue(const rapidjson::Value &val, Map &map);

    template <typename Vec>
    typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type toJsonValue(rapidjson::Document &doc, const Vec &vec, rapidjson::Value &val);

    template <typename Vec>
    typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type fromJsonValue(const rapidjson::Value &val, Vec &vec);

    // ideally would like to have the below functions in the .cpp file, but
    // unfortunately because of how templates work they have to be here.

    template <typename Map>
    typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Document &doc, const Map &map, rapidjson::Value &val)
    {
      val.SetObject();

      auto &al = doc.GetAllocator();

      for (const auto &i : map)
      {
        rapidjson::Value k;
        rapidjson::Value m;
        toJsonValue(doc, i.first, k);
        toJsonValue(doc, i.second, m);
        val.AddMember(k, m, al);
      }
    }

    template <typename Map>
    typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type fromJsonValue(const rapidjson::Value &val, Map &map)
    {
      if (!val.IsObject())
      {
        throw WRONG_TYPE("json object");
      }

      auto itr = val.MemberBegin();

      while (itr != val.MemberEnd())
      {
        typename Map::key_type k;
        typename Map::mapped_type m;
        fromJsonValue(itr->name, k);
        fromJsonValue(itr->value, m);
        map.emplace(k, m);
        ++itr;
      }
    }

    template <typename Vec>
    typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type toJsonValue(rapidjson::Document &doc, const Vec &vec, rapidjson::Value &val)
    {
      val.SetArray();

      for (const auto &t : vec)
      {
        rapidjson::Value v;
        toJsonValue(doc, t, v);
        val.PushBack(v, doc.GetAllocator());
      }
    }

    template <typename Vec>
    typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type fromJsonValue(const rapidjson::Value &val, Vec &vec)
    {
      if (!val.IsArray())
      {
        throw WRONG_TYPE("json array");
      }

      for (rapidjson::SizeType i = 0; i < val.Size(); i++)
      {
        typename Vec::value_type v;
        fromJsonValue(val[i], v);
        vec.push_back(v);
      }
    }

  } // namespace json

} // namespace cryptonote
